#include "WiFi.h"
#include "WebServer.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "DHT11.h"
#include "sqlite3.h"

// DHT11 GPIO
#define DHTPIN 21

// SD GPIO
#define SCK  18
#define MISO  19
#define MOSI  23
#define CS  5
// WIFI config
const char *ssid = "ESP32-WEB";
const char *password = "12345678";

IPAddress local_IP(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

// init dht
// 存储 DHT 数据
int gTemperature = 0;
int gHumidity = 0;
unsigned long lastDHTRead = 0;
const unsigned long DHT_INTERVAL = 2000; // 每2秒更新一次
DHT11 dht11(DHTPIN);

// web server
WebServer server(80);  // 80是默认的HTTP端口

// db info
sqlite3 *db;

// mount SD card
int mount_sd() {
  if(!SD.begin(CS)){
    Serial.println("Card Mount Failed");
    return -1;
  }
  uint8_t cardType = SD.cardType();

  if(cardType == CARD_NONE){
    Serial.println("No SD card attached");
    return -1;
  }

  Serial.print("SD Card mount success, card type is: ");
  if(cardType == CARD_MMC){
    Serial.print("MMC");
  } else if(cardType == CARD_SD){
    Serial.print("SDSC");
  } else if(cardType == CARD_SDHC){
    Serial.print("SDHC");
  } else {
    Serial.print("UNKNOWN");
  }

  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf(", card size: %lluMB\n", cardSize);
  return 0;
}

// open db
int openDB(const char *filename, sqlite3 **db) {
  int rc = sqlite3_open(filename, db);
  if (rc) {
      Serial.printf("Can't open database: %s\n", sqlite3_errmsg(*db));
      return rc;
  } else {
      Serial.printf("Opened database successfully\n");
  }
  return rc;
}

// ------ tools start ------
String buildPath(const String &dir, const String &name) {
  String path = "/";
  if (dir.length() > 0) {
    path += dir;
    if (!path.endsWith("/")) path += "/";
  }
  path += name;
  return path;
}

String buildResponse(bool success, String message, String dataJson = "{}") {
  String json = "{";
  json += "\"success\": " + String(success ? "true" : "false") + ",";
  json += "\"message\": \"" + message + "\",";
  json += "\"data\": " + dataJson;
  json += "}";
  return json;
}

// 公共 JSON 响应函数
void sendJsonResponse(int code, bool success, const String &message, const String &dataKV = "") {
  String json = "{";
  json += "\"success\": " + String(success ? "true" : "false");
  json += ", \"message\": \"" + message + "\"";
  json += ", \"data\": {";
  if (dataKV.length() > 0) {
    json += dataKV;
  }
  json += "}";
  json += "}";
  server.send(code, "application/json", json);
}

// ------ tools end ----

// dht data update
void updateDHT() {
  unsigned long now = millis();
  if (now - lastDHTRead >= DHT_INTERVAL) {
    lastDHTRead = now;

    int t = 0, h = 0;
    int result = dht11.readTemperatureHumidity(t, h);
    if (result == 0) {
      gTemperature = t;
      gHumidity = h;
      Serial.printf("DHT Updated -> T=%d °C, H=%d %%\n", gTemperature, gHumidity);
    } else {
      Serial.println(DHT11::getErrorString(result));
    }
  }
}

// ----- register router start -----
void registerRoute() {
  server.on("/", HTTP_GET, []() { sendPageHandle("/public/index.html"); });
  server.on("/about", HTTP_GET, []() { sendPageHandle("/public/about.html"); });
  server.on("/sd-info", HTTP_GET, handleSDInfo);
  server.on("/dht", HTTP_GET, handleDHT);
  server.on("/upload", HTTP_POST, []() {
    server.send(200);
  }, handleFileUpload);
  server.on("/download", HTTP_GET, handleFileDownload);
  server.on("/file", HTTP_DELETE, handleFileDelete);
  server.on("/mkdir", HTTP_POST, handleMkdir);
  server.on("/dir", HTTP_DELETE, handleRmdir);
  server.on("/dir", HTTP_GET, handleListDir);
  // db
  server.on("/user-list", HTTP_GET, handleListUser);
  server.on("/user", HTTP_GET, handleGetUser);
  server.on("/user", HTTP_POST, handleCreateUser);
  server.on("/user", HTTP_PUT, handleUpdateUser);
  server.on("/user", HTTP_DELETE, handleDelUser);
}

void sendPageHandle(const char *filePath) {
  File file = SD.open(filePath);
  if (file) {
    server.streamFile(file, "text/html");
    file.close();
  } else {
    server.send(404, "text/html", "<h1>Page not found</h1>");
  }
}

void handleSDInfo() {
  if (!SD.cardType()) {
    sendJsonResponse(500, false, "No SD card attached");
    return;
  }

  String type;
  switch (SD.cardType()) {
    case CARD_MMC:  type = "MMC"; break;
    case CARD_SD:   type = "SDSC"; break;
    case CARD_SDHC: type = "SDHC"; break;
    default:        type = "UNKNOWN"; break;
  }

  String dataKV = "";
  dataKV += "\"type\": \"" + type + "\"";
  dataKV += ", \"totalBytes\": " + String(SD.cardSize());
  dataKV += ", \"usedBytes\": " + String(SD.usedBytes());

  sendJsonResponse(200, true, "SD card info", dataKV);
}

void handleDHT() {
  String dataKV = "\"temperature\": " + String(gTemperature) + ", \"humidity\": " + String(gHumidity);
  sendJsonResponse(200, true, "DHT11 data", dataKV);
}

// file operate
void handleFileUpload() {
  HTTPUpload& upload = server.upload();
  if (upload.status == UPLOAD_FILE_START) {
    String filename = "/data/" + upload.filename;
    File file = SD.open(filename, FILE_WRITE);
    if (!file) {
      sendJsonResponse(500, false, "Failed to open file for writing");
      return;
    }
    file.close();
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    File file = SD.open("/data/" + upload.filename, FILE_APPEND);
    if (file) {
      file.write(upload.buf, upload.currentSize);
      file.close();
    }
  } else if (upload.status == UPLOAD_FILE_END) {
    String dataKV = "\"filename\": \"" + upload.filename + "\"";
    sendJsonResponse(200, true, "File uploaded", dataKV);
  }
}

void handleFileDownload() {
  if (!server.hasArg("name")) {
    sendJsonResponse(400, false, "Missing file name");
    return;
  }

  String filename = "/data/" + server.arg("name");
  File file = SD.open(filename, FILE_READ);
  if (!file) {
    sendJsonResponse(404, false, "File not found");
    return;
  }

  server.streamFile(file, "application/octet-stream");
  file.close();
}

void handleFileDelete() {
  if (!server.hasArg("name")) {
    sendJsonResponse(400, false, "Missing file name");
    return;
  }

  String filename = "/data/" + server.arg("name");
  if (SD.exists(filename)) {
    SD.remove(filename);
    String dataKV = "\"filename\": \"" + server.arg("name") + "\"";
    sendJsonResponse(200, true, "File deleted", dataKV);
  } else {
    sendJsonResponse(404, false, "File not found");
  }
}

void handleMkdir() {
  if (!server.hasArg("name")) {
    sendJsonResponse(400, false, "Missing folder name");
    return;
  }

  String folderName = "/data/" + server.arg("name");
  if (SD.mkdir(folderName)) {
    String dataKV = "\"folder\": \"" + server.arg("name") + "\"";
    sendJsonResponse(200, true, "Folder created", dataKV);
  } else {
    sendJsonResponse(500, false, "Failed to create folder");
  }
}

void handleRmdir() {
  if (!server.hasArg("name")) {
    sendJsonResponse(400, false, "Missing folder name");
    return;
  }

  String folderName = "/data/" + server.arg("name");

  if (!SD.exists(folderName)) {
    sendJsonResponse(404, false, "Folder not found");
    return;
  }

  if (SD.rmdir(folderName)) {
    String dataKV = "\"folder\": \"" + server.arg("name") + "\"";
    sendJsonResponse(200, true, "Folder deleted", dataKV);
  } else {
    sendJsonResponse(500, false, "Failed to delete folder (maybe not empty)");
  }
}

void handleListDir() {
  if (!server.hasArg("path")) {
    sendJsonResponse(400, false, "Missing path parameter");
    return;
  }

  String path = "/data/" + server.arg("path");
  if (!SD.exists(path)) {
    sendJsonResponse(404, false, "Path not found");
    return;
  }

  File dir = SD.open(path);
  if (!dir || !dir.isDirectory()) {
    sendJsonResponse(400, false, "Not a directory");
    return;
  }

  String dataJson = "";

  File file = dir.openNextFile();
  int count = 0;
  while (file) {
    if (count > 0) dataJson += ",";
    String name = file.name();
    name.replace("\"", "\\\""); // 避免 JSON 崩溃

    if (file.isDirectory()) {
      dataJson += "\"" + name + "\": \"dir\"";
    } else {
      dataJson += "\"" + name + "\": \"file\"";
    }

    file = dir.openNextFile();
    count++;
  }

  sendJsonResponse(200, true, "Directory listed", dataJson);
}

// db operate
void handleListUser() {
  String data = "[";
  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(db, "SELECT id, username, email, created_at FROM users;", -1, &stmt, NULL) == SQLITE_OK) {
    bool first = true;
    while (sqlite3_step(stmt) == SQLITE_ROW) {
      if (!first) data += ",";
      data += "{";
      data += "\"id\": " + String(sqlite3_column_int(stmt, 0)) + ",";
      data += "\"username\": \"" + String((const char*)sqlite3_column_text(stmt, 1)) + "\",";
      data += "\"email\": \"" + String((const char*)sqlite3_column_text(stmt, 2)) + "\",";
      data += "\"created_at\": \"" + String((const char*)sqlite3_column_text(stmt, 3)) + "\"";
      data += "}";
      first = false;
    }
    sqlite3_finalize(stmt);
  }
  data += "]";
  server.send(200, "application/json", buildResponse(true, "User list", data));
}

void handleGetUser() {
  if (!server.hasArg("id")) {
    server.send(400, "application/json", buildResponse(false, "Missing id"));
    return;
  }
  int id = server.arg("id").toInt();

  sqlite3_stmt *stmt;
  String data = "{}";
  if (sqlite3_prepare_v2(db, "SELECT id, username, email, created_at FROM users WHERE id=?;", -1, &stmt, NULL) == SQLITE_OK) {
    sqlite3_bind_int(stmt, 1, id);
    if (sqlite3_step(stmt) == SQLITE_ROW) {
      data = "{";
      data += "\"id\": " + String(sqlite3_column_int(stmt, 0)) + ",";
      data += "\"username\": \"" + String((const char*)sqlite3_column_text(stmt, 1)) + "\",";
      data += "\"email\": \"" + String((const char*)sqlite3_column_text(stmt, 2)) + "\",";
      data += "\"created_at\": \"" + String((const char*)sqlite3_column_text(stmt, 3)) + "\"";
      data += "}";
    }
    sqlite3_finalize(stmt);
  }
  server.send(200, "application/json", buildResponse(true, "User info", data));
}

void handleCreateUser() {
  if (!(server.hasArg("username") && server.hasArg("password") && server.hasArg("email"))) {
    server.send(400, "application/json", buildResponse(false, "Missing parameters"));
    return;
  }
  String username = server.arg("username");
  String password = server.arg("password");
  String email = server.arg("email");

  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(db, "INSERT INTO users (username,password,email) VALUES (?,?,?);", -1, &stmt, NULL) == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, password.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, email.c_str(), -1, SQLITE_STATIC);
    if (sqlite3_step(stmt) == SQLITE_DONE) {
      server.send(200, "application/json", buildResponse(true, "User created"));
    } else {
      server.send(500, "application/json", buildResponse(false, "Insert failed"));
    }
    sqlite3_finalize(stmt);
  }
}

void handleUpdateUser() {
  if (!server.hasArg("id")) {
    server.send(400, "application/json", buildResponse(false, "Missing id"));
    return;
  }
  int id = server.arg("id").toInt();
  String username = server.arg("username");
  String password = server.arg("password");
  String email = server.arg("email");

  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(db, "UPDATE users SET username=?, password=?, email=? WHERE id=?;", -1, &stmt, NULL) == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, password.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 3, email.c_str(), -1, SQLITE_STATIC);
    sqlite3_bind_int(stmt, 4, id);
    if (sqlite3_step(stmt) == SQLITE_DONE) {
      server.send(200, "application/json", buildResponse(true, "User updated"));
    } else {
      server.send(500, "application/json", buildResponse(false, "Update failed"));
    }
    sqlite3_finalize(stmt);
  }
}

void handleDelUser() {
  if (!server.hasArg("id")) {
    server.send(400, "application/json", buildResponse(false, "Missing id"));
    return;
  }
  int id = server.arg("id").toInt();

  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(db, "DELETE FROM users WHERE id=?;", -1, &stmt, NULL) == SQLITE_OK) {
    sqlite3_bind_int(stmt, 1, id);
    if (sqlite3_step(stmt) == SQLITE_DONE) {
      server.send(200, "application/json", buildResponse(true, "User deleted"));
    } else {
      server.send(500, "application/json", buildResponse(false, "Delete failed"));
    }
    sqlite3_finalize(stmt);
  }
}

// ----- register router end -----

void setup() {
  Serial.begin(115200);
  // mount SD
  if (mount_sd() == -1) {
    return;
  }
  sqlite3_initialize();
  // Open database
  if (openDB("/sd/db/test.db", &db)) {
    return;
  }

  // set esp mode is AP
  if (!WiFi.softAP(ssid, password)) {
    Serial.println("AP Failed to start");
    return;
  }
  // set ip
  if (!WiFi.softAPConfig(local_IP, gateway, subnet)) {
    Serial.println("Failed to configure AP IP address");
    return;
  }
  Serial.println("Access Point Started");
  Serial.print("IP Address: ");
  Serial.println(WiFi.softAPIP());  // 输出设置的静态 IP 地址

  // start web server
  registerRoute();
  server.begin();
  
  Serial.println("Web server started.");
}

void loop() {
  // web
  server.handleClient();
  // update dht
  updateDHT();
}
